﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Visuals;
using System.Drawing;
using Silvermoon.Core;
using Silvermoon.Utils;
using Silvermoon.Images;
using Silvermoon.OpenGL;
using Silvermoon.Controls.Interfaces;
using System.Windows.Forms;
using Silvermoon.Controls.Base;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Control to input text with clipboard support.
    /// </summary>
    public class TextBox : NotifyEventControl, IInputEventBehavior, IScrollEventBehavior, IContextMenuBehavior
    {
        #region constants

        public readonly int TextBoxCornerRadius = Control.Scale(14);
        public readonly int LeftRightOffset = Control.ScaleX(12);
        public static readonly Color DefaultTextColor = SystemColors.WindowText;
        public static readonly Color CursorColor = ColorUtil.AlphaColor(DefaultTextColor, 210);
        public static readonly Color HighlightTextColor = SystemColors.HighlightText;
        public static readonly Color DefaultBorderColor = Color.Blue;
        public static readonly Color HighlightColor = Color.DeepSkyBlue;

        #endregion
        #region ctor

        protected static Timer cursorTimer = new Timer { Interval = 600 };
        private static TextBoxToolTip staticToolTip;

        protected static TextBoxToolTip DefaultToolTip
        {
            get
            {
                if (staticToolTip == null) staticToolTip = new TextBoxToolTip();
                return staticToolTip;
            }
        }


        static TextBox()
        {
            cursorTimer.Tick += new EventHandler(CursorTick);
        }


        public TextBox()
            : base()
        {
            borderLine = true;
            MaxLength = 32767;
            stringVisual = new StringVisual { Color = DefaultTextColor, LineMode = LineMode.SingleLine };
            background = new BackgroundVisual { Colors = ColorUtil.SingleColor(Color.White) };
            background.Texture = SysImages.TextBox;
            visualCursor = new RectangleVisual { Visible = false, Color = CursorColor };
            visualSelection = new RectangleVisual { Visible = false, Color = HighlightColor };

            Visuals = new Visual[] { background, visualSelection, stringVisual, visualCursor };
            TextColor = DefaultTextColor;
            //            ContextMenu = TextBoxMenu.Instance;
        }

        #endregion
        #region Fields

        private ToolTipBase toolTip;
        private int displayStartIndex;
        private string text = string.Empty;
        private bool borderLine;
        private int downIndex;
        private static TextBox focusedTextBox;
        private StringVisual stringVisual;
        private BackgroundVisual background;
        private RectangleVisual visualCursor;
        private RectangleVisual visualSelection;
        private Texture backgroundTexture;
        private Texture selectedBackgroundTexture;

        private int selStart;
        private int selLen;

        #endregion
        #region Properties

        public ToolTipBase ToolTip
        {
            get { return toolTip ?? TextBox.DefaultToolTip; }
            set { toolTip = value; }
        }

        /// <summary>
        /// Gets or sets the texture used to render the background when not focused.
        /// </summary>
        public Texture BackgroundTexture
        {
            get { return backgroundTexture ?? SysImages.TextBox; }
            set
            {
                if (backgroundTexture != value)
                {
                    backgroundTexture = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        /// <summary>
        /// Gets or sets the texture used to render the background when focused.
        /// </summary>
        public Texture SelectedBackgroundTexture
        {
            get { return selectedBackgroundTexture ?? SysImages.SelectedTextBox; }
            set
            {
                if (selectedBackgroundTexture != value)
                {
                    selectedBackgroundTexture = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        public FontSprite Font { get { return stringVisual.Font; } set { if (stringVisual.Font != value) { stringVisual.Font = value; Invalidate(InvalidationFlag.Appearance); } } }

        protected static TextBox FocusedTextBox
        {
            get { return focusedTextBox; }
            set
            {
                if (focusedTextBox != value)
                {
                    if (focusedTextBox != null) focusedTextBox.IsCursorVisible = false;
                    focusedTextBox = value;

                    if (value != null) value.IsCursorVisible = true;
                    cursorTimer.Enabled = value != null;
                }
            }
        }

        /// <summary>
        /// Gets or sets the character to to replace with the real character of the text.
        /// </summary>
        public char PasswordChar { get; set; }

        /// <summary>
        /// Gets or sets the text to edit.
        /// </summary>
        public string Text { get { return text; } set { value = ValidateTextValue(value); if (text != value) { OnTextChanging(Text, value); text = value; SetDisplayText(); } } }

        /// <summary>
        /// Gets or sets the color to use for the text.
        /// </summary>
        public Color ForegroundColor { get { return stringVisual.Color; } set { stringVisual.Color = value; } }

        /// <summary>
        /// Gets or sets whether the text is read only.
        /// </summary>
        public bool ReadOnly { get; set; }

        /// <summary>
        /// Gets or sets the maximum number of characters the user can type or paste into the text box control.
        /// The default is 32767;
        /// </summary>
        public virtual int MaxLength { get; set; }

        /// <summary>
        /// Gets or sets whether the text box has a border line.
        /// </summary>
        public bool ShowBorderLine { get { return borderLine; } set { if (borderLine != value) { borderLine = value; Invalidate(InvalidationFlag.Appearance); } } }

        /// <summary>
        /// Gets or sets a value indicating the currently selected text in the control.
        /// </summary>
        public int SelectionStart
        {
            get { return selStart; }
            set
            {
                if (selStart != value) Select(value, 0);
            }
        }

        /// <summary>
        /// Gets or sets the number of characters selected in the text box.
        /// </summary>
        public int SelectionLength
        {
            get { return selLen; }
            set
            {
                if (selLen != value) Select(selStart, value);
            }
        }

        /// <summary>
        /// Gets or sets the number of characters selected in the text box.
        /// </summary>
        public virtual string SelectedText
        {
            get { return Text.Substring(selStart, selLen); }
            set { InsertText(value); }
        }

        /// <summary>
        /// Gets whether the cursor is currently shown.
        /// </summary>
        public bool IsCursorVisible
        {
            get { return visualCursor.Visible && Enabled && !ReadOnly; }
            set
            {
                if (visualCursor.Visible != value)
                {
                    visualCursor.Visible = value;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        #endregion
        #region Methods

        static void CursorTick(object sender, EventArgs e)
        {
            TextBox tb = FocusedTextBox;
            if (tb != null)
            {
                if (tb.selLen == 0)
                {
                    tb.IsCursorVisible ^= true;
                }
                else tb.IsCursorVisible = false;
            }
        }


        protected override void OnLayout()
        {
            visualCursor.Visible = IsCursorVisible;
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Margin | InvalidationFlag.Appearance))
            {
                AdjustSize();
            }
            background.Texture = IsFocused ? SelectedBackgroundTexture : BackgroundTexture;
            //this.background.BorderSize = ShowBorderLine ? Control.Scale(3) : 0;
            base.OnLayout();
        }

        private void AdjustSize()
        {
            int textHeight = stringVisual.Font.CharHeight;
            background.Size = Control.Scale(Size);
            visualCursor.Size = Control.ScaledSize(2, textHeight);
            stringVisual.Location = Control.ScaledPoint(LeftRightOffset, (Size.Height - textHeight) / 2);
            stringVisual.Size = Control.ScaledSize(Width - LeftRightOffset * 2, Height - 4);
        }

        private string ValidateTextValue(string value)
        {
            if (value == null) value = string.Empty;
            if (value.Length > MaxLength) value = value.Substring(0, MaxLength);

            return value;
        }

        private void SetDisplayText()
        {
            stringVisual.Text = GetVisibleText();
        }


        /// <summary>
        /// Occurs when the text is changing.
        /// </summary>
        /// <param name="oldText">The previous text value.</param>
        /// <param name="newText">The new text value.</param>
        protected virtual void OnTextChanging(string oldText, string newText)
        {
            Select(selStart, 0);
            this.OnSizeChanged();
            text = newText;
            if (TextChanged != null) TextChanged(this, EventArgs.Empty);
        }

        public override void OnGotFocus()
        {
            if (Enabled && Visible && !ReadOnly) FocusedTextBox = this;
            //BorderColor = Color.Red;
            background.Invalidate();
            base.OnGotFocus();
        }

        public override void OnLostFocus()
        {
            Select(0, 0);
            FocusedTextBox = null;
            //BorderColor = DefaultBorderColor;
            background.Invalidate();
            if (ToolTip != null) ToolTip.Visible = false;
            base.OnLostFocus();
        }

        public override void PreLoad()
        {
            stringVisual.PreLoad();
            base.PreLoad();
        }

        private int ValidateStartSel(int value)
        {
            if (value < 0) return 0;
            string text = Text ?? string.Empty;
            if (value > text.Length) return text.Length;
            return value;
        }

        private string GetDisplayText()
        {
            return PasswordChar == '\0' ? text : new string(PasswordChar, text.Length);
        }

        private string GetVisibleText()
        {
            return GetDisplayText().Substring(displayStartIndex);
        }

        private Rectangle GetSelectBounds(int startSel, int selCount)
        {
            if (startSel < displayStartIndex) displayStartIndex = Math.Max(0, startSel);

            string text = GetVisibleText();
            startSel -= Math.Max(0, displayStartIndex);

            int fontHeight = stringVisual.TextSize.Height;

            string leftStr = text.Substring(0, startSel);

            int selLeft = !string.IsNullOrEmpty(leftStr) ? stringVisual.MeasureString(leftStr).Width : 0;

            int width = 1;
            if (selCount > 0)
            {
                int rightSel = startSel + selCount;
                if (rightSel > text.Length) selCount = text.Length - startSel;
                string rightStr = text.Substring(startSel, selCount);
                width = !string.IsNullOrEmpty(rightStr) ? stringVisual.MeasureString(rightStr).Width : 0;
            }
            Rectangle r = new Rectangle(selLeft, 0, width, fontHeight);
            r.Offset(LeftRightOffset, (Size.Height - fontHeight) / 2);
            int maxW = Width - 2 * LeftRightOffset;
            if (r.Width > maxW) r.Width = maxW;

            return r;
        }

        /// <summary>
        /// elects a range of text in the text box. 
        /// </summary>
        /// <param name="start">The position of the first character in the current text selection within the textbox.</param>
        /// <param name="length">The number of characters to select.</param>
        public void Select(int start, int length)
        {
            start = ValidateStartSel(start);
            if (this.selStart != start || this.selLen != length)
            {
                string text = Text ?? string.Empty;
                int max = length + start;
                if (max > text.Length) length = max - start;
                this.selStart = start;
                this.selLen = length;
                IsCursorVisible = true;

                if (start < displayStartIndex) displayStartIndex = Math.Max(0, start);
                int maxW = Width - LeftRightOffset * 2;
                int maxL = text.Length;
                int n = start + length;
                while (stringVisual.MeasureString(GetMaxVisibleText(n - displayStartIndex)).Width > maxW)
                {
                    displayStartIndex++;
                    if (displayStartIndex >= maxL) break;
                }

                Rectangle bounds = GetSelectBounds(start, length);
                SetDisplayText();
                if (length > 0)
                {
                    visualSelection.Bounds = bounds;
                    visualSelection.Visible = true;
                    IsCursorVisible = false;
                }
                else
                {
                    visualSelection.Visible = false;
                    IsCursorVisible = true;
                    visualCursor.Location = bounds.Location;
                }
                Invalidate(InvalidationFlag.LayoutAppearance);
            }
        }

        private string GetMaxVisibleText(int n)
        {
            string s = GetVisibleText();
            int l = Math.Min(s.Length, n);
            return s.Substring(0, l);

        }

        /// <summary>
        ///   Selects all text in the text box.
        /// </summary>
        public void SelectAll()
        {
            Select(0, Text.Length);
        }

        private int IndexFromPoint(Point point)
        {
            int index = displayStartIndex;
            if (Text.Length > 0)
            {
                int left = LeftRightOffset;
                int x = point.X;
                string text = GetVisibleText();
                for (int i = 1; i <= text.Length; i++)
                {
                    string s = text.Substring(0, i);
                    int w = stringVisual.MeasureString(s).Width;
                    if ((w + left) >= x) break;
                    index++;
                }
            }

            index = ValidateStartSel(index);
            return index;
        }


        public void Paste()
        {
            string s = System.Windows.Forms.Clipboard.GetDataObject().GetData(typeof(string)) as string;
            if (s != null) InsertText(s);
        }

        public void Copy()
        {
            string text = SelectedText;
            System.Windows.Forms.Clipboard.SetDataObject(text);
        }

        public void Cut()
        {
            string text = SelectedText;
            InsertText("");
            System.Windows.Forms.Clipboard.SetDataObject(text);
        }

        protected override void OnVisibleChanged()
        {
            if (!Visible) ToolTip.Visible = false;
            base.OnVisibleChanged();
        }

        protected override void OnEnabledChanged()
        {
            if (!Enabled && ToolTip != null) ToolTip.Visible = false;
            base.OnEnabledChanged();
        }


        #endregion
        #region IInputEventBehavior Members

        public virtual void OnKeyDown(KeyEventArgs e)
        {
            if (!e.Handled)
            {
                e.Handled = true;
                if (ReadOnly) return;
                switch (e.KeyCode)
                {
                    case Keys.Left: LeftKey(); break;
                    case Keys.Right: RightKey(); break;
                    case Keys.Home: HomeKey(); break;
                    case Keys.End: EndKey(); break;
                    case Keys.Delete: Delete(); break;
                    default: e.Handled = false; break;
                }
            }
        }

        private void EndKey()
        {
            Select(0, Text.Length);
        }

        private void HomeKey()
        {
            Select(0, 0);
        }

        private void RightKey()
        {
            int w = Math.Max(1, selLen);
            Select(selStart + w, 0);
        }

        private void LeftKey()
        {
            Select(selStart - 1, 0);
        }

        private void Delete()
        {
            if (selStart < Text.Length)
            {
                Text = Text.Remove(selStart, selLen > 0 ? selLen : 1);
                Select(selStart, 0);
            }
        }

        private void EnterKey()
        {
            Silvermoon.Core.Screen.Current.Focus(null);
        }

        private void TabKey()
        {

        }

        private void EscapeKey()
        {
        }

        private void Backspace()
        {
            if (selLen > 0) Delete();
            else
            {
                if (selStart > 0)
                {
                    int start = ValidateStartSel(selStart - 1);
                    Text = Text.Remove(start, selLen + 1);
                    Select(start, 0);
                }
            }
        }

        public void InsertChar(char c)
        {
            if (!Enabled) return;
            string text = Text;
            int startSel = this.selStart;
            if (selLen > 0) text = text.Remove(startSel, selLen);
            Text = text.Insert(startSel, c.ToString());
            Select(startSel + 1, 0);
        }

        private void InsertText(string text)
        {
            if (!Enabled) return;
            string currentText = Text;
            int startSel = this.selStart;
            if (selLen > 0) currentText = currentText.Remove(startSel, selLen);
            Text = currentText.Insert(startSel, text);
            Select(startSel + text.Length, 0);
        }

        public virtual void OnKeyUp(KeyEventArgs e)
        {
            e.Handled = true;
            if (ReadOnly) return;
        }

        public virtual void OnKeyPress(KeyPressEventArgs e)
        {
            if (!e.Handled && !ReadOnly)
            {
                char c = e.KeyChar;
                switch (c)
                {
                    case '\r': EnterKey(); e.Handled = false; break;
                    case '\x1b': EscapeKey(); break;
                    case '\t': TabKey(); break;
                    case '\b': Backspace(); break;
                    default: InsertChar(c); e.Handled = true; break;
                }
            }
            e.Handled = true;
        }

        #endregion
        #region IMouseEventBehavior


        public override void OnMouseClick(MouseClickEventArgs e)
        {
            base.OnMouseClick(e);
        }


        public override void OnMouseDown(MouseDownEventArgs e)
        {
            base.OnMouseDown(e);
            downIndex = IndexFromPoint(e.ScreenToClient(e.Position));
            //ShowToolTip();
        }

        public override void OnMouseUp(MouseUpEventArgs e)
        {
            base.OnMouseUp(e);
            int index = IndexFromPoint(e.ScreenToClient(e.DownPosition));
            int index2 = IndexFromPoint(e.ScreenToClient(e.UpPosition));
            if (index == index2)
            {
                Select(index, 0);
            }
            e.AllowDoubleClick = true;
            e.Handled = true;
        }

        public override void OnMouseMove(MouseMoveEventArgs e)
        {
            base.OnMouseMove(e);
            if (Enabled)
            {
                int index = IndexFromPoint(e.ScreenToClient(e.Position));
                if (index != downIndex)
                {
                    int idx0 = Math.Min(index, downIndex);
                    int idx1 = Math.Max(index, downIndex);
                    int selCount = idx1 - idx0;
                    int startSel = idx0;
                    if (this.selLen != selCount || this.selStart != startSel)
                    {
                        Select(startSel, selCount);
                        if (selCount > 1) ShowToolTip();
                    }
                }
            }
        }

        protected virtual void ShowToolTip()
        {
            if (Enabled && Visible && ToolTip != null && ToolTip.Enabled)
            {
                ToolTip.Show(this);
            }
        }

        public override void OnDblClick()
        {
            if (Enabled)
            {
                SelectAll();
                ShowToolTip();
            }
        }


        #endregion
        #region IScrollEventBehavior Members

        public void OnScroll(ScrollEventArgs e)
        {
        }

        public void OnBeginScrollData(BeginScrollEventArgs e)
        {

            int ax = Math.Abs(e.Position.X - e.StartPosition.X);
            int ay = Math.Abs(e.Position.Y - e.StartPosition.Y);
            if (ax > ay)
            {
                e.Cancel = true;
                e.Handled = true;
            }
            else
            {
                if (ToolTip != null) ToolTip.Visible = false;
            }
        }

        public void OnEndScrollData(EndScrollEventArgs e)
        {
        }

        #endregion
        #region IContextMenu Members

        public ContextMenu ContextMenu { get; set; }

        #endregion
        #region events

        public event EventHandler TextChanged;
        #endregion
    }
}
